#ifndef LUNA_H
#define LUNA_H 1

//	http://www.lua.org/notes/ltn005.html

/*-----------------------------------------------------------------------------
 * First :
    class Foo {
	public:
		Foo(lua_State *L)		{	printf("in Constructor\n");	}
		int foo(lua_State *L)	{	printf("in foo\n");			}
		~Foo()					{	printf("in destructor\n");	}

		static udptr_s	root;
		static const char classname[];
		static const Luna<Foo>::RegType_s func_register[];
    };

	Luna<Foo>::udptr_s	root;
    const char Foo::classname[] = "Foo";
    const Luna<Foo>::RegType_s Foo::func_register[] = {
		{ "foo", &Foo::foo },
		{ 0 }
    };

 * Then :
    Luna<Foo>::Register(L);
    
 * From Lua :
    local foo = Foo()
    foo:foo()

 * Result :
    in Constructor
    in foo
    in destructor
-----------------------------------------------------------------------------*/

#define LUNA_DECLARE_CLASS(cls)			\
	static Luna<cls>::udptr_s	root;	\
	static const char classname[];		\
	static const Luna<cls>::RegType_s func_register[];


#define LUNA_IMP_BEGIN(cls)					\
	const char cls::classname[] = #cls;		\
	Luna<cls>::udptr_s	cls::root;			\
	const Luna<cls>::RegType_s cls::func_register[] = {
	
#define LUNA_IMP_METHOD(cls, method)	{ #method, &cls::method },
#define LUNA_IMP_END(cls)				{ 0 } };

/*-----------------------------------------------------------------------------
	Luna itself :
-----------------------------------------------------------------------------*/

template<typename T> class Luna {

		public: struct RegType_s {
			const char *name;
			int(T::*mfunc)(lua_State*);
		};
	
		public: struct udptr_s {
			udptr_s ( void ) { ptr=NULL; next=prev=this; }
			T *ptr;
			udptr_s	*next, *prev;
		};
		
		private: static void insert_node_after ( udptr_s *node, udptr_s *newnode ) {
			node->next->prev	=	newnode;
			newnode->next		=	node->next;
			newnode->prev		=	node;
			node->next			=	newnode;
			
		}
		
		private: static void remove_node ( udptr_s *node ) {
			udptr_s *next		=	node->next;
			udptr_s *prev		=	node->prev;
			next->prev			=	prev;
			prev->next			=	next;
			node->next			=	NULL;
			node->prev			=	NULL;
		}
	
		/*---------------------------------------------------------------------
		*	Register() - registers object stuff 
		---------------------------------------------------------------------*/
		public: static void Register(lua_State *L) {
		
			lua_pushcfunction(L, &Luna<T>::Constructor);
			lua_setglobal(L, T::classname); // T() in lua will make a new instance.

			//	register methods :
			RegisterMetatable(L);
		}

		/*---------------------------------------------------------------------
		*	Unregister()	- kill all stuff
		*					- UD will keep just numbers
		---------------------------------------------------------------------*/
		public: static void Unregister(lua_State *L) {

			udptr_s *node = T::root.next;

			//	dispose all objects :		
			while (node!=&T::root) {
				delete node->ptr;
				node->ptr = NULL;
				remove_node( node );
				node = T::root.next;
			}

			//	reset "__gc" method :
			luaL_newmetatable(L, T::classname);
			lua_pushnil(L);
			lua_setfield(L, -2, "__gc");
			lua_pop(L, 1);
		}

		/*---------------------------------------------------------------------
		*	RegisterMetatable()
		---------------------------------------------------------------------*/
		private: static void RegisterMetatable(lua_State *L) {

			//	create or use existing metatable (now it is on top) :
			luaL_newmetatable(L, T::classname);

			//	set "__gc" method :
			lua_pushcfunction(L, &Luna<T>::GCollect); 		lua_setfield(L, -2, "__gc");
			lua_pushcfunction(L, &Luna<T>::Class);	  		lua_setfield(L, -2, "class");
			lua_pushcfunction(L, &Luna<T>::Dispose);  		lua_setfield(L, -2, "dispose");
			lua_pushcfunction(L, &Luna<T>::IsDisposed);		lua_setfield(L, -2, "is_disposed");

			//	register methods :
			for (int i = 0; T::func_register[i].name; i++) {
				lua_pushnumber(L, i);
				lua_pushcclosure(L, &Luna<T>::CallProxy, 1);
				lua_setfield(L, -2, T::func_register[i].name);
			}

			//	metatable.__index = metatable (Lua pattern) :
			lua_pushvalue(L, -1);				// push metatable
			lua_setfield(L, -2, "__index");		// metatable.__index = metatable

			//	pop metatable :
			lua_pop(L, 1);
		}

		/*---------------------------------------------------------------------
		*	Constructor()  - creates object on the top of the stack
		---------------------------------------------------------------------*/
		public: static int Constructor(lua_State *L) {
			return PushObject(L, new T(L));
		}

		/*---------------------------------------------------------------------
		*	PushObject()	- pushes object on the top of the stack 
		---------------------------------------------------------------------*/
		public: static int PushObject(lua_State *L, T *obj) {
		
			ASSERT(obj);
			
			void		*ptr	=	lua_newuserdata(L, sizeof(udptr_s));
			udptr_s*	udp		=	new (ptr) udptr_s;

			udp->ptr			=	obj;
								
			luaL_newmetatable(L, T::classname);
			lua_setmetatable(L, -2);
			
			insert_node_after( &T::root, udp );
					
			return 1;
		}

		/*---------------------------------------------------------------------
		*	ToObject()	 - tries to cast object, if fail returns NULL
		---------------------------------------------------------------------*/
		public: static T* ToObject( lua_State *L, int idx ) {
		
			if (!IsObject(L, idx)) {
				return NULL;
			}
			
			udptr_s*	udp	=	static_cast<udptr_s*>(lua_touserdata(L, idx));

			return udp->ptr;
		}

		/*---------------------------------------------------------------------
		*	Require()	 - pattern to get necessary argument
		---------------------------------------------------------------------*/
		public: static T* Require( lua_State *L, int idx, const char *what ) {

			T *obj	=	ToObject(L, idx);
			if (!obj) {
				LuaArgumentError(L, idx, T::classname, what);
			}

			return obj;
		}
		
		/*---------------------------------------------------------------------
		*	IsObject()	- check object type and it's accessability 
		*				  i.e. disposed object can't be accessed
		---------------------------------------------------------------------*/
		public: static bool IsObject( lua_State *L, int idx) {
		
			udptr_s *udp = static_cast<udptr_s*>( LuaToUData(L, idx, T::classname) );
			
			if (!udp)		{	return false;	}				
			if (!udp->ptr)	{	return false;	}
			
			return true;
		}
		
		
		/*---------------------------------------------------------------------
		*	CallProxy()
		---------------------------------------------------------------------*/
		private: static int CallProxy(lua_State *L) {
	
			try {
				int i = (int)lua_tonumber(L, lua_upvalueindex(1)); // which function?

				udptr_s *udp = static_cast<udptr_s*>( LuaRequireUData(L, 1, T::classname, "self" ) );
				
				if (!udp->ptr) {
					luaL_error(L, "attempt to access disposed object %s", T::classname);
				}

				lua_remove(L, 1);	//	remove 'self'
				lua_pushnil(L);		//	push nil to keep same stack height

				return (udp->ptr->*(T::func_register[i].mfunc))(L); // execute method
				
			} catch (exception &e) {
				luaL_error(L, "exception : %s", e.what());
				return 0;
			}
		}
		
		/*---------------------------------------------------------------------
		*	Class()
		---------------------------------------------------------------------*/
		private: static int Class(lua_State *L) {
			lua_pushstring(L, T::classname);
			return 1;
		}
		
		/*---------------------------------------------------------------------
		*	Dispose()
		---------------------------------------------------------------------*/
		private: static int Dispose(lua_State *L) {

			udptr_s *udp = static_cast<udptr_s*>( LuaRequireUData(L, 1, T::classname, "self" ) );
			
			if (udp->ptr) {
				remove_node(udp);
				delete udp->ptr;
				udp->ptr = NULL;
			}
			
			return 0;
		}
		
		/*---------------------------------------------------------------------
		*	IsDisposed()
		---------------------------------------------------------------------*/
		private: static int IsDisposed(lua_State *L) {

			udptr_s *udp = static_cast<udptr_s*>( LuaRequireUData(L, 1, T::classname, "self" ) );
			
			if (udp->ptr) {
				lua_pushboolean(L, 1);
			} else {
				lua_pushboolean(L, 0);
			}
			return 1;
		}
		
		/*---------------------------------------------------------------------
		*	GCollect()
		---------------------------------------------------------------------*/
		private: static int GCollect(lua_State *L) {
		
			udptr_s* udp = static_cast<udptr_s*>(luaL_checkudata(L, -1, T::classname));
			
			if (udp->ptr) {
				remove_node( udp );
				delete udp->ptr;
				udp->ptr = NULL;
			}
			
			return 0;
		}
		
	};


#endif /* LUNA_H */

